# region imports
from AlgorithmImports import *
# endregion

# Import datetime
import datetime

class SwimmingGreenBull(QCAlgorithm):

    def initialize(self):

        # Set start date 
        self.set_start_date(2026, 3, 20)

        # Set cash
        self.set_cash(100000)

        # Add future
        future_object = self.add_future(ticker = Futures.Energy.CRUDE_OIL_WTI, resolution = Resolution.MINUTE, fill_forward = False, extended_market_hours = True)

        # Set filter
        future_object.set_filter(min_expiry_days = 0, max_expiry_days = 60)

        # Register SPY
        SPY = self.add_equity(ticker = "SPY")

        # Time to store volume
        self.time_to_store_volume = False

        # Symbols list
        self.symbols_list = []

        # Total volume today
        self.total_volume_today = {}

        # Minute by minute volume
        self.minute_by_minute_volume = {}

        # Highest average minute-by-minute volume
        self.highest_average_minute_by_minute_volume = None

        # Last time alert logged
        self.last_time_alert_logged = None

        # Run alerts
        self.run_alerts = False

        # Scheduled event for start of daily break
        self.schedule.on(
        self.date_rules.every_day(future_object.symbol),
        self.time_rules.at(hour = 17, minute = 0, second = 0),
        self.reset_total_volume
    )

        # Scheduled event for start of regular trading hours for SPY
        self.schedule.on(
        self.date_rules.every_day(SPY.symbol),
        self.time_rules.after_market_open(symbol = SPY.symbol, minutes_after_open = 0),
        self.regular_trading_hours_open_for_SPY
    )

        # Scheduled event for end of regular trading hours for SPY
        self.schedule.on(
        self.date_rules.every_day(SPY.symbol),
        self.time_rules.before_market_close(symbol = SPY.symbol, minutes_before_close = 0),
        self.regular_trading_hours_close_for_SPY
    )

        # Scheduled event for 5 PM on Fridays
        self.schedule.on(
        self.date_rules.week_end(symbol = future_object.symbol, days_offset = 0),
        self.time_rules.at(hour = 17, minute = 0, second = 0),
        self.reset_minute_by_minute_volume
    )

        # Scheduled event for 7 PM on Sundays
        self.schedule.on(
        self.date_rules.week_start(symbol = future_object.symbol, days_offset = 0),
        self.time_rules.at(hour = 19, minute = 0, second = 0),
        self.start_running_alerts
    )

        # Set warm up
        self.set_warm_up(time_span = datetime.timedelta(days = 14))

    def reset_total_volume(self):

        # Loop total volume today dictionary
        for symbol in self.total_volume_today:

            # Reset 
            self.total_volume_today[symbol] = 0
    
    def regular_trading_hours_open_for_SPY(self):

        # Set time to store volume to True
        self.time_to_store_volume = True
    
    def regular_trading_hours_close_for_SPY(self):

        # Set time to store volume to False
        self.time_to_store_volume = False

    def reset_minute_by_minute_volume(self):

        # Average minute-by-minute volume of contracts
        average_minute_by_minute_volume_of_contracts = []

        # Loop
        for symbol in self.minute_by_minute_volume:

            # Calculate average minute-by-minute volume
            average_minute_by_minute_volume = sum(self.minute_by_minute_volume[symbol]) / len(self.minute_by_minute_volume[symbol])

            # Store in local list
            average_minute_by_minute_volume_of_contracts.append(average_minute_by_minute_volume)

            # Reset
            self.minute_by_minute_volume[symbol] = []
        
        # Get highest average minute-by-minute volume from local list
        self.highest_average_minute_by_minute_volume = max(average_minute_by_minute_volume_of_contracts)

        # Set run alerts variable to False
        self.run_alerts = False

    def start_running_alerts(self):

        # Set run alerts variable to True
        self.run_alerts = True
    
    def on_securities_changed(self, changes):

        # Loop added securities
        for contract in changes.added_securities:

            # Check if it's not a continuous futures contract
            if "/" not in contract.symbol.value:

                # Check if it's not SPY
                if contract.symbol.value != "SPY":

                    # Store symbol
                    self.symbols_list.append(contract.symbol)

                    # Store in total volume today dictionary
                    self.total_volume_today[contract.symbol] = 0

                    # Store in minute by minute volume dictionary
                    self.minute_by_minute_volume[contract.symbol] = []
        
        # Loop removed securities
        for contract in changes.removed_securities:

            # Remove from symbols list
            self.symbols_list.remove(contract.symbol)

            # Remove from total volume today dictionary
            self.total_volume_today.pop(contract.symbol)

            # Remove from minute by minute volume dictionary
            self.minute_by_minute_volume.pop(contract.symbol)

    def on_data(self, data: Slice):
        
        # Loop symbols list
        for symbol in self.symbols_list:

            # If contract is traded
            if data.bars.contains_key(symbol) == True:

                # Get tradebar
                trade_bar = data.bars[symbol]

                # Volume
                symbol_volume = trade_bar.volume

                # Store volume
                self.total_volume_today[symbol] += symbol_volume

                # Store volume in minute by minute volume list
                self.minute_by_minute_volume[symbol].append(symbol_volume)

                # Check if not warming up
                if self.is_warming_up == False:

                    # If run alerts is True
                    if self.run_alerts == True:
        
                        # Get symbol of contract with highest total volume
                        symbol_with_highest_total_volume = max(self.total_volume_today, key=self.total_volume_today.get)

                        # If current iteration symbol is symbol with highest total volume
                        if symbol == symbol_with_highest_total_volume:

                            # If symbol volume is 3x greater than highest average minute by minute volume
                            if symbol_volume > self.highest_average_minute_by_minute_volume * 3:

                                # If last alert time is None
                                if self.last_time_alert_logged is None:

                                    # Log volume spike
                                    self.log("Volume spike for: " + symbol.value)

                                    # Store current time
                                    self.last_time_alert_logged = self.time
                                
                                # Else
                                else:

                                    # If current time more than 2 hours from last alert time
                                    if self.time > self.last_time_alert_logged + datetime.timedelta(hours = 2):

                                        # Log volume spike
                                        self.log("Volume spike for: " + symbol.value)

                                        # Store current time
                                        self.last_time_alert_logged = self.time